﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace WindowsGame1.GameComponents
{
    class DungeonFloor
    {
        const int GRID_SIZE = 10;

        Random rand = new Random();
        public Room[,] floor = new Room[GRID_SIZE, GRID_SIZE];
        int total_rooms = 0;

        public Point start_room;
        public Point end_room;
        List<Point> occupied_rooms = new List<Point>();
        List<Point> goal_rooms = new List<Point>();

        RandomRoomGenerator rrg;

        public void GenerateRandomFloor(int initialDifficulty)
        {
            rrg = new RandomRoomGenerator();
            
            //select random starting location
            //generate an empty room
            //for each adjacent empty square, randomly select connected or not
            //if d10 is less than 10 - sum of number of connected rooms , it is connected

            //if connected
            //generate a new room in that square
            //set the exits for both rooms to connect
            //recursion until a room doesn't or cannot connect, increasing difficulty
            //mark the last room as a goal

            //for each adjacent non-connected occupied square
            //maybe connect
            //maybe place trap doors

            //for each empty square at the end of this process
            //1 in 4 chance of placing a secret room there

            //for each room marked as a goal
            //select one randomly to contain the exit
            //the others should contain objectives or events
            start_room = new Point(rand.Next(1, GRID_SIZE - 1), rand.Next(1, GRID_SIZE - 1));
            PlanPath(0, 0, start_room.X, start_room.Y);

            



            //for each adjacent non-connected occupied square
            //maybe connect
            //maybe place trap doors
            List<Point> empty_adj_rooms = new List<Point>();
            for (int i = 0; i < goal_rooms.Count; i++)
            {
                Point room = goal_rooms[i];
                empty_adj_rooms = GetAdjacencies(room, false);
                if (rand.Next(0, 6 - empty_adj_rooms.Count) == 0 && empty_adj_rooms.Count > 2 && goal_rooms.Count > 5)
                {
                    empty_adj_rooms.Shuffle();
                    Point newroom = empty_adj_rooms[0];

                    Connect(room.X, room.Y, newroom.X, newroom.Y);
                    goal_rooms.RemoveAt(i);
                    i--;
                }
            }

            //try to place the exit far from the entrance
            int dist = 0;
            foreach (Point m_room in goal_rooms)
            {
                if (rand.Next(4) != 3 && Math.Abs(m_room.X - start_room.X) + Math.Abs(m_room.Y - start_room.Y) > dist)
                {
                    dist = Math.Abs(m_room.X - start_room.X) + Math.Abs(m_room.Y - start_room.Y);
                    end_room = m_room;
                }
            }

            int x = 100;
        }

        private void PlanPath(int connection, int difficulty, int x, int y)
        {
            int connections = connection;





            int sum_connections = 0;
            bool connected = false;

            if (floor[x, y] == null)
                floor[x, y] = rrg.GenerateRoom(difficulty);
            else
                return;

            int bias_i = 0;
            int bias_j = 0;

            if (floor[x, y].north)
            {
                connections++;
                connected = true;
                bias_j--;
            }
            if (floor[x, y].south)
            {
                connections++;
                connected = true;
                bias_j++;
            }
            if (floor[x, y].east)
            {
                connections++;
                connected = true;
                bias_i++;
            }
            if (floor[x, y].west)
            {
                connections++;
                connected = true;
                bias_i--;
            }

            //i shouldn't make 4-way rooms here
            if (connections > 3)
                return;

            Point bias = new Point(bias_i, bias_j);

            occupied_rooms.Add(new Point(x, y));
            total_rooms++;


            //for each adjacent empty square, randomly select connected or not
            //if d10 is less than 10 - sum of number of connected rooms , it is connected

            //if connected
            //generate a new room in that square
            //set the exits for both rooms to connect
            //recursion until a room doesn't or cannot connect, increasing difficulty
            //mark the last room as a goal
            List<Point> empty_adj_rooms = new List<Point>();

            if (difficulty < (GRID_SIZE * 3) && (occupied_rooms.Count < (GRID_SIZE * GRID_SIZE) / 3 || goal_rooms.Count < 10))
            {
                empty_adj_rooms = GetAdjacencies(new Point(x, y), true);
                empty_adj_rooms.Shuffle();

                if (empty_adj_rooms.Contains(bias))
                {
                    if (rand.Next(3) != 1)
                    {
                        connections++;

                        connected = true;

                        PlanPath(1, difficulty + 1, x + bias.X, y + bias.Y);
                        Connect(x, y, bias.X, bias.Y);
                    }
                }

                foreach (Point p in empty_adj_rooms)
                {
                    if ((connection == 0 || rand.Next(10) < 7 - connections))
                    {
                        connections += 3;

                        connected = true;

                        PlanPath(1, difficulty + 1, x + p.X, y + p.Y);
                        Connect(x, y, p.X, p.Y);
                    }
                }
            }

            if (!connected)
            {
                goal_rooms.Add(new Point(x, y));
            }

            //for each empty square at the end of this process
            //(# adjacent rooms) in 4 chance of placing a hidden room there

            //for each room marked as a goal
            //select one randomly to contain the exit
            //the others should contain objectives or events

        }

        private void Connect(int x, int y, int i, int j)
        {
            if (i == -1)
            {
                floor[x, y].west = true;
                floor[x + i, y + j].east = true;
            }
            if (i == 1)
            {
                floor[x, y].east = true;
                floor[x + i, y + j].west = true;
            }
            if (j == 1)
            {
                floor[x, y].south = true;
                floor[x + i, y + j].north = true;
            }
            if (j == -1)
            {
                floor[x, y].north = true;
                floor[x + i, y + j].south = true;
            }
        }

        private List<Point> GetAdjacencies(Point room, Boolean empty)
        {
            List<Point> adj_rooms = new List<Point>();

            for (int i = -1; i <= 1; i += 2)
            {
                if (room.X + i >= GRID_SIZE || room.X + i < 0)
                    continue;

                if ((floor[room.X + i, room.Y] == null) == empty)
                {
                    adj_rooms.Add(new Point(i, 0));
                }
            }

            for (int j = -1; j <= 1; j += 2)
            {
                if (room.Y + j >= GRID_SIZE || room.Y + j < 0)
                    continue;

                if ((floor[room.X, room.Y + j] == null) == empty)
                {
                    adj_rooms.Add(new Point(0, j));
                }
            }

            return adj_rooms;
        }

        public override string ToString()
        {
            string output = "\n";

            for (int j = 0; j < GRID_SIZE; j++)
            {
                for (int line = 0; line < 3; line++)
                {
                    for (int i = 0; i < GRID_SIZE; i++)
                    {
                        string my_room = " ";
                        Point compare = new Point(i, j);
                        if (start_room.Equals(compare))
                            my_room = "s";
                        else if (end_room.Equals(compare))
                            my_room = "e";
                        else if (goal_rooms.Contains(compare))
                            my_room = "!";

                        if (floor[i, j] == null)
                        {
                            output += "XXX";
                        }
                        else
                        {

                            switch (line)
                            {
                                case 0:
                                    if (floor[i, j] != null && floor[i, j].north)
                                        output += "= =";
                                    else
                                        output += "===";
                                    break;
                                case 1:
                                    if (floor[i, j] != null && floor[i, j].west)
                                        output += " ";
                                    else
                                        output += "=";

                                    output += my_room;

                                    if (floor[i, j] != null && floor[i, j].east)
                                        output += " ";
                                    else
                                        output += "=";

                                    break;
                                case 2:
                                    if (floor[i, j] != null && floor[i, j].south)
                                        output += "= =";
                                    else
                                        output += "===";
                                    break;
                            }
                        }
                    }
                    output += '\n';
                }
            }

            return output;
        }
    }
}
